/**
 * 
 */
package info.sswap.ontologies.modularity;

import info.sswap.ontologies.modularity.owlapi.OWLAPIModule;
import info.sswap.ontologies.modularity.repo.OntologyRepository;
import info.sswap.ontologies.modularity.repo.OntologyRepositoryException;
import info.sswap.ontologies.modularity.repo.RepositoryEntry;
import info.sswap.ontologies.util.URIUtils;

import java.net.URI;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

import org.openjena.atlas.lib.SetUtils;
import org.semanticweb.owlapi.apibinding.OWLManager;
import org.semanticweb.owlapi.model.IRI;
import org.semanticweb.owlapi.model.OWLAnnotationProperty;
import org.semanticweb.owlapi.model.OWLAxiom;
import org.semanticweb.owlapi.model.OWLClass;
import org.semanticweb.owlapi.model.OWLDataProperty;
import org.semanticweb.owlapi.model.OWLDatatype;
import org.semanticweb.owlapi.model.OWLObjectProperty;
import org.semanticweb.owlapi.model.OWLOntology;
import org.semanticweb.owlapi.model.OWLOntologyCreationException;
import org.semanticweb.owlapi.model.OWLOntologyManager;

/**
 * @author Pabel Klinov
 *
 */
public class SSWAPTermResolver implements TermResolver {

	private final OntologyRepository mOntoRepo;
	
	public SSWAPTermResolver(OntologyRepository repo) {
		
		if (repo == null) {
			throw new IllegalArgumentException("Ontology repository must be set");
		}
		else {
			mOntoRepo = repo;
		}
	}
	
	@Override
	public Module resolveTerm(URI term, URI definedBy) throws ModuleExtractionException {
		OntologyDescriptor[] descrs = getOntologyDescriptors(term, definedBy);
		Module main = new OWLAPIModule();
		
		try {
			for (OntologyDescriptor descr : descrs) {
				Module nextModule = null;
				
				try {
					if (!processableOnline(descr)) {
						// no luck TODO queue up the term for later dereferencing
						continue;
					} else if (descr.getRepositoryEntry() == null) {
						// dereference online
						nextModule = loadAxiomsForTerm(term, descr.getPhysicalURI());
					} else {
						// go into the repo
						nextModule = lookUpInRepository(term, descr.getRepositoryEntry());
					}
				} catch (OWLOntologyCreationException e) {
					// TODO log it?
					e.printStackTrace();
				} catch (OntologyRepositoryException e) {
					// TODO log it?
					e.printStackTrace();
				}
				
				if (nextModule != null) {
					main = main.union(nextModule);
				}
			}
		} finally {
			//clean up, close all connections (XML or RDF) that AD could be using
			for (OntologyDescriptor  descr : descrs) {
				if (descr.getRepositoryEntry() != null) {
					descr.getRepositoryEntry().close();
				}
			}
		}

		if (main.isEmpty() && descrs.length > 0) {
			//no axioms found for the term, assume it's an annotation property
			main = createModuleForAnnotation(term);
		}
		
		return main;
	}

	private Module createModuleForAnnotation(URI term) {
		OWLOntologyManager mgr = OWLManager.createOWLOntologyManager();
		OWLAnnotationProperty ap = mgr.getOWLDataFactory().getOWLAnnotationProperty(IRI.create(term));
		Set<OWLAxiom> axioms = Collections.<OWLAxiom>singleton(mgr.getOWLDataFactory().getOWLDeclarationAxiom(ap));
		
		try {
			return new OWLAPIModule(mgr.createOntology(axioms));
		} catch (OWLOntologyCreationException e) {
			//can't get here
			return new OWLAPIModule();
		}
	}

	private Module lookUpInRepository(URI term, RepositoryEntry repoEntry) throws OntologyRepositoryException {
		return repoEntry.getAxiomsForTerm(term);
	}

	private Module loadAxiomsForTerm(URI term, URI physicalURI) throws OWLOntologyCreationException {
		OWLOntologyManager mgr = OWLManager.createOWLOntologyManager();
		OWLOntology ontology = mgr.loadOntologyFromOntologyDocument(IRI.create(physicalURI));
		IRI termIRI = IRI.create(term);
		Set<? extends OWLAxiom> decl = null;
		//try find definitions for the term as class, object property, data property, etc.
		OWLClass clazz = mgr.getOWLDataFactory().getOWLClass(termIRI);
		
		if ((decl = SetUtils.union(ontology.getAxioms(clazz), ontology.getDeclarationAxioms(clazz))).isEmpty()) {
			OWLObjectProperty op = mgr.getOWLDataFactory().getOWLObjectProperty(termIRI);
		
			if ((decl = SetUtils.union(ontology.getAxioms(op), ontology.getDeclarationAxioms(op))).isEmpty()) {
				OWLDataProperty dp = mgr.getOWLDataFactory().getOWLDataProperty(termIRI);
				
				if ((decl = SetUtils.union(ontology.getAxioms(dp), ontology.getDeclarationAxioms(dp))).isEmpty()) {
					OWLAnnotationProperty ap = mgr.getOWLDataFactory().getOWLAnnotationProperty(termIRI);
					
					if ((decl = SetUtils.union(ontology.getDeclarationAxioms(ap), ontology.getDeclarationAxioms(ap))).isEmpty()) {
						OWLDatatype datatype = mgr.getOWLDataFactory().getOWLDatatype(termIRI);
						
						decl = SetUtils.union(ontology.getAxioms(datatype), ontology.getDeclarationAxioms(datatype));
					}
				}
			}
		}
		
		return new OWLAPIModule(mgr.createOntology(new HashSet<OWLAxiom>(decl)));
	}
	
	private boolean processableOnline(OntologyDescriptor descr) {
		return descr.getRepositoryEntry() != null || isDeredeferenceable(descr.getPhysicalURI());
	}

	private boolean isDeredeferenceable(URI uri) {
		String uriStr = uri.toString();
		
		return uriStr.startsWith(Constants.SSWAPMEET_PREFIX) || uriStr.startsWith("http://localhost");
	}

	private OntologyDescriptor[] getOntologyDescriptors(URI term, URI definedBy) {
		
		if (definedBy != null) {
			return new OntologyDescriptor[]{new OntologyDescriptor(definedBy, definedBy)};
		}
		else {
			try {
				Collection<RepositoryEntry> entries = mOntoRepo.getEntriesForTerm(term);
				
				if (entries.isEmpty()) {
					entries = mOntoRepo.getEntriesForTerm(URI.create(URIUtils.getNamespace(term)));
				}

				if (entries.isEmpty()) {
					return new OntologyDescriptor[] { new OntologyDescriptor(term, term) };
				} else {

					OntologyDescriptor[] descrs = new OntologyDescriptor[entries.size()];
					int i = 0;

					for (RepositoryEntry entry : entries) {
						descrs[i++] = new OntologyDescriptor(entry);
					}

					return descrs;
				}
			} catch (OntologyRepositoryException e) {
				// TODO Handle it
				e.printStackTrace();
				
				return new OntologyDescriptor[]{};
			}
		}
	}

}
